Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.

Breaking: Implement parseForESLint() function #412

Merged
merged 2 commits into from
Dec 13, 2017
Merged

Conversation

JamesHenry
Copy link
Member

@JamesHenry JamesHenry commented Dec 12, 2017

This PR implements a parseForESLint() function.

This will be preferred by ESLint, and so we can use it to implement tweaks to the AST which are only required in an ESLint context. The idea is that this will be used only where absolutely necessary.

The most pressing case is that of a method's value (FunctionExpression) having no body in some valid situations in TypeScript.

For now, the simplest solution is just to namespace the node.type as TSEmptyBody${node.type} only in the case that we are parsing for ESLint.

Prettier's usage will be completely unaffected because it will continue to use parse().

Fixes #400 #389 #402 #253

🎉

Other minor notes:

  • I updated the README, there were some out of date issues in there
  • I removed old babel-eslint issue numbers from that spec file as they were confusing
  • Removed the erroneously committed yarn.lock file

@j-f1
Copy link
Contributor

j-f1 commented Dec 12, 2017

Are you sure TSEmptyBody${foo} is the best solution? IMHO the FunctionDeclaration variant should be TSFunctionOverloadDeclaration, and the MethodDeclaration variant should be TSMethodOverloadDeclaration, since that is what they are used for.

@JamesHenry
Copy link
Member Author

@j-f1 I was personally just thinking how the name is very unimportant at this stage. The only stakeholders that could possibly care about these particular node names are custom ESLint TypeScript rule authors (I know you are one of them :D) and all of that goes through eslint-plugin-typescript right now. Any non-ESLint users of this parser won't even come across those nodes.

Having a more situational name like TSFunctionOverloadDeclaration, describing what it does rather than what it is, would simply require quite a bit more code to cover all the cases.

This seemed like the more straightforward starting point, and we could certainly change it in the future if it makes sense to.

@mysticatea
Copy link
Member

In my understand, this PR adds 2 node types:

  • TSEmptyBodyFunctionExpression
  • TSEmptyBodyFunctionDeclaration

Looks good to me.

By the way, I think that some cases should be throw syntax errors. For example:

const obj = {
    a()
}

@JamesHenry
Copy link
Member Author

Yes, this is the case in general with the TypeScript compiler (which powers this parser). It is hyper lenient during the parsing phase because it has language services which come later in the pipeline to provide feedback to developers whilst they author TypeScript.

Adding our own strictness on top of that would need to come later in a separate PR, but it might be worth doing at some point.

@j-f1
Copy link
Contributor

j-f1 commented Dec 12, 2017

Having a more situational name like TSFunctionOverloadDeclaration, describing what it does rather than what it is, would simply require quite a bit more code to cover all the cases.

In what other cases would you get one of these bodyless functions?

@JamesHenry
Copy link
Member Author

E.g.

interface I {
    func(): string;
}

declare function myFunc(): boolean;

abstract class C {
    abstract foo(): number;
}

}
);
});
// it("no-implicit-globals in module", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own knowledge, why are these tests commented out?

Copy link
Member Author

@JamesHenry JamesHenry Dec 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't solved them yet. They need to be solved by a custom Resolver I think, but I didn't want to lose them when I cut the custom resolver out of this PR in favour of this new flag.

It would be fair enough to request I take them out of the PR, but laziness made me want to keep them in as reminders 😄

@j-f1
Copy link
Contributor

j-f1 commented Dec 12, 2017

@JamesHenry

interface I {
    func(): string; // TSMethodSignature
}

declare function myFunc(): boolean; // DeclareFunction

abstract class C {
    abstract foo(): number; // TSAbstractMethodDeclaration with FunctionExpression value
}

How about TSMethodAnnotation and TSFunctionAnnotation as types instead?

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM aside from one comment.

parser.js Outdated
@@ -177,6 +185,12 @@ exports.version = require("./package.json").version;

exports.parse = parse;

exports.parseForESLint = function parseForESLint(code, options) {
options.parseForESLint = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be best to avoid mutating the options object provided by the user. (This was also a problem in eslint/js#329.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which case I would be forced to change the signature of parse() to accept an optional third argument. There's no reason that would be an issue, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would be an issue.

If you don't want the change to be visible to the public API, you could create a helper function, e.g. parseMaybeForESlint(code, options, internalOptions), and have parse call parseMaybeForESLint(code, options, { isForESLint: false }) and have parseForESLint call parseMaybeForESLint(code, options, { isForESLint: true }).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about just defensively cloning the options so the user can't see the mutation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also do options = Object.assign({}, options, { parseForESLint: true }). Or insert options = Object.create(options) before this line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems strange to have parseForESLint be mixed with the user-provided options object. For example, if the user added parseForESLint: true to their options object for some reason, they could cause strange behavior.

@JamesHenry
Copy link
Member Author

@not-an-aardvark PTAL at my latest commit b273fca

@JamesHenry JamesHenry merged commit aec31cb into master Dec 13, 2017
@JamesHenry JamesHenry deleted the parse-for-eslint branch December 13, 2017 10:46
@JamesHenry
Copy link
Member Author

@j-f1 eslint-plugin-typescript is the only possible stakeholder in those node types right now, so we can discuss again if it becomes an issue with that, and they can easily be changed. I really wanted to get this in ASAP and feel TSEmptyBodyFunctionExpression TSEmptyBodyFunctionDeclaration are at least very clear as a starting point, given they are replacing FunctionExpression and FunctionDeclaration.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants